package net.pimpas.operation.serviceprovider;

import net.pimpas.core.configuration.exception.ConfigurationException;
import net.pimpas.core.serviceprovider.ServiceProviderAnnotation;
import net.pimpas.core.serviceprovider.instancetype.ServiceProviderInstanceType;
import net.pimpas.logging.serviceprovider.LoggingServiceProvider;
import net.pimpas.logging.serviceprovider.LoggingServiceProviderFactoryImpl;
import net.pimpas.operation.exception.OperationException;
import net.pimpas.operation.operation.Operation;
import net.pimpas.operation.operation.OperationAnnotation;
import net.pimpas.operation.operation.OperationFactory;
import net.pimpas.util.reflection.ReflectionUtil;

@ServiceProviderAnnotation(instanceType=ServiceProviderInstanceType.NORMAL,
		serviceProviderType=OperationServiceProvider.class
)
public class OperationServiceProviderImpl implements OperationServiceProvider<Object, Operation> {

	private static final LoggingServiceProvider logger = LoggingServiceProviderFactoryImpl.getLoggingServiceProvider(OperationServiceProviderImpl.class);
	
	public Object call(Operation op, Object... parameters) throws OperationException {
		return this.call(op, null, parameters);
	}

	public void config() throws ConfigurationException {
		
	}

	public Object call(String name, Object... parameters) throws OperationException {
		Operation op = null;
		return this.call(name, op, parameters);
	}

	public Object call(String name, Class operationType, Object... parameters) throws OperationException {
		return this.call(name, operationType, null, parameters);
	}

	@SuppressWarnings("static-access")
	public Object call(String name, Operation parent, Object... parameters) throws OperationException {
		Operation op = OperationFactory.getInstance().find(name);
		if(op == null)
			throw new OperationException("CAN'T FIND OPERATION WITH NAME '"+name+"'.");
		return this.call(op, parent, parameters);
	}

	public Object call(Operation op, Operation parent, Object... parameters) throws OperationException {
		
		String name = null;
		
		if(ReflectionUtil.checkAnnotationInClass(OperationAnnotation.class, op.getClass()))
			name = op.getClass().getAnnotation(OperationAnnotation.class).name();
		else
			name = op.getClass().getSimpleName();
		
		logger.debug("Initializing Operation ["+name+"]");
		try {
			op.init(parameters);
		} catch (OperationException e) {
			logger.error("Error while initializing Operation ["+name+"]");
			throw e;
		}
		
		logger.debug("Starting Operation ["+name+"]");
		
		op.start();
		
		Object result = null;
		Throwable ex = null;
		try {
			logger.debug("Calling Operation ["+name+"]");
			result = op.call();
			logger.debug("Commiting Operation ["+name+"]");
		} catch (Exception e) {
			ex = e;
			logger.fatal("Error in Operation ["+name+"]", e);
			logger.debug("RollingBack Operation ["+name+"]");
			op.rollBack();
		} finally {
			op.end();
			logger.debug("Finalizing Operation ["+name+"]");
			if(ex != null) throw new OperationException(ex);
		}
		
		logger.debug("Returning "+result+" for Operation {"+name+"}");
		return result;
	}

	@SuppressWarnings("static-access")
	public Object call(String name, Operation parent, Class operationType, Object... parameters) throws OperationException {
		Operation op = OperationFactory.getInstance().find(operationType, name);
		if(op == null)
			throw new OperationException("CAN'T FIND OPERATION WITH NAME '"+name+"'.");
		return this.call(op, parent, parameters);
	}

	public boolean isTerminated() {
		return false;
	}
}